package ghostbanlistd;

import java.net.InetAddress;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;

public class Database extends Thread implements BanListener {
	ArrayList<Connection> connections;
	String host;
	String username;
	String password;
	
	Bans bans;

	public Database(Bans bans) {
		this.bans = bans;
		
		//configuration
		host = Config.getString("db_host", null);
		username = Config.getString("db_username", null);
		password = Config.getString("db_password", null);
	}

	public void init() {
		connections = new ArrayList<Connection>();
		
		try {
			Class.forName("com.mysql.jdbc.Driver");
		} catch(ClassNotFoundException cnfe) {
			Main.println("[SQL] MySQL driver cannot be found: " + cnfe.getLocalizedMessage());
			
			if(Main.DEBUG) {
				cnfe.printStackTrace();
			}
		}
		
		bans.addListener(this);
	}
	
	//gets a connection
	public Connection connection() {
		synchronized(connections) {
			if(connections.isEmpty()) {
				try {
					Main.println("[SQL] Creating new connection...");
					connections.add(DriverManager.getConnection(host, username, password));
				}
				catch(SQLException e) {
					Main.println("[SQL] Unable to connect to mysql database: " + e.getLocalizedMessage());
					
					if(Main.DEBUG) {
						e.printStackTrace();
					}
				}
			}

			//might still have no connections if creating a connection failed
			if(connections.isEmpty()) return null;
			else {
				Connection connection = connections.remove(0);
				try {
					if(connection.isClosed()) {
						return connection();
					} else {
						return connection;
					}
				} catch(SQLException e) {
					return connection();
				}
			}
		}
	}
	
	public void connectionReady(Connection connection) {
		synchronized(connections) {
			connections.add(connection);
		}
	}

	//authenticates an API key and IP address
	// 0: authentication failed
	// 1: read access granted
	// 2: read-write access granted
	public int authenticate(byte[] apiKey, InetAddress address) {
		try {
			Connection connection = connection();
			
			PreparedStatement statement = connection.prepareStatement("SELECT address, write FROM users WHERE apikey = ?");
			statement.setString(1, Util.hexEncode(apiKey));
			
			ResultSet result = statement.executeQuery();
			
			if(result.next()) {
				String dbAddress = result.getString(1);
				boolean write = result.getBoolean(2);
				
				connectionReady(connection);
				
				//if database address is not wildcard, then compare addresses
				if(!dbAddress.equals("") && !dbAddress.equals("*")) {
					byte[] addressBytes = address.getAddress();
					String[] parts = dbAddress.split(".");
					
					//if parts contains more address parts than address bytes, fail
					if(parts.length > addressBytes.length) return 0;
					
					for(int i = 0; i < addressBytes.length; i++) {
						//if we haven't hit wildcard yet and we ran out of bytes, fail
						if(i >= parts.length) return 0;
						
						//if we have hit a wildcard, success
						if(parts[i].equals("*")) {
							break;
						}
						
						//compare addresses for this block, and fail if inequal
						if(Integer.parseInt(parts[i]) != Util.unsignedByte(addressBytes[i])) {
							return 0;
						}
					}
				}
				
				if(write) return 2;
				else return 1;
			} else {
				connectionReady(connection);
				return 0;
			}
		} catch(SQLException e) {
			Main.println("[SQL] Error: " + e.getLocalizedMessage());
			return 0;
		}
	}
	
	public void banAdded(Ban ban) {
		if(ban.database_id == -1) {
			try {
				Connection connection = connection();
				
				PreparedStatement statement = connection.prepareStatement("INSERT INTO bans (address, reasonCode, reasonString, entryTime, apiID) VALUES (?, ?, ?, ?, ?)", Statement.RETURN_GENERATED_KEYS);
				statement.setInt(1, Util.byteArrayToInt(ban.address));
				statement.setInt(2, ban.reasonCode);
				statement.setString(3, ban.reasonString);
				statement.setLong(4, ban.entryTime);
				statement.setInt(5, ban.apiID);
				
				int affectedRows = statement.executeUpdate();
				if(affectedRows == 0) {
					Main.println("[SQL] Ban insert failed, no rows affected");
					return;
				}
				
				ResultSet result = statement.getGeneratedKeys();
				if(result.next()) {
					ban.database_id = result.getInt(1);
				} else {
					Main.println("[SQL] Ban insert may have failed, insert ID not returned");
				}
				
				result.close();
				connectionReady(connection);
			} catch(SQLException e) {
				Main.println("[SQL] Error: " + e.getLocalizedMessage());
			}
		}
	}
	
	public void banRemoved(Ban ban) {
		if(ban.database_id != -1) {
			try {
				Connection connection = connection();
				
				PreparedStatement statement = connection.prepareStatement("DELETE FROM bans WHERE id = ?");
				statement.setInt(1, ban.database_id);
				statement.execute();
				
				connectionReady(connection);
			} catch(SQLException e) {
				Main.println("[SQL] Error: " + e.getLocalizedMessage());
			}
		}
	}
	
	public void populateBans() {
		try {
			Connection connection = connection();
			
			PreparedStatement statement = connection.prepareStatement("SELECT * FROM bans");
			ResultSet result = statement.executeQuery();
			
			while(result.next()) {
				int database_id = result.getInt("id");
				byte[] address = Util.intToByteArray(result.getInt("address"));
				int reasonCode = result.getInt("reasonCode");
				String reasonString = result.getString("reasonString");
				long entryTime = result.getLong("entryTime");
				int apiID = result.getInt("apiID");
				
				Ban ban = new Ban(address, reasonCode, reasonString, apiID, entryTime, database_id);
				bans.addBan(ban);
			}
			
			result.close();
			connectionReady(connection);
		} catch(SQLException e) {
			Main.println("[SQL] Error: " + e.getLocalizedMessage());
		}
	}
	
	public void run() {
		//get list of bans
		populateBans();
		
		while(true) {
			
		}
	}
}